#ifndef STX_CORE_ENDIANNESS_HPP
#define STX_CORE_ENDIANNESS_HPP

#include <stx/core/primitive_types.hpp>
#include <cstddef>

//  Warning: platform must have exact uint32 type - not greater than 32 bits.
//  STX_HAS_EXACT_INT32

#define STX_HOST_BYTE_ORDER          (*(stx::uint32*) "\x01\x02\x03\x04")
#define STX_LITTLE_ENDIAN_BYTE_ORDER 0x04030201
#define STX_BIG_ENDIAN_BYTE_ORDER    0x01020304
#define STX_PDP_ENDIAN_BYTE_ORDER    0x02010403

//  LE - Little Endian
//  BE - Big Endian
//  PE - PDP Endian

#define STX_LE_BE_CONVERSION_INT16(x) \
    (((x & 0x00ff) << 8) | \
     ((x & 0xff00) >> 8))

#define STX_LE_BE_CONVERSION_INT32(x) \
    (((x & 0x000000ff) << 24) | \
     ((x & 0x0000ff00) <<  8) | \
     ((x & 0x00ff0000) >>  8) | \
     ((x & 0xff000000) >> 24))

#define STX_LE_BE_CONVERSION_INT64(x) \
    (((x & 0x00000000000000ffLL) << 56) | \
     ((x & 0x000000000000ff00LL) << 40) | \
     ((x & 0x0000000000ff0000LL) << 24) | \
     ((x & 0x00000000ff000000LL) <<  8) | \
     ((x & 0x000000ff00000000LL) >>  8) | \
     ((x & 0x0000ff0000000000LL) >> 24) | \
     ((x & 0x00ff000000000000LL) >> 40) | \
     ((x & 0xff00000000000000LL) >> 56))

#define STX_LE_PE_CONVERSION_INT16(x) (x)

#define STX_LE_PE_CONVERSION_INT32(x) \
    (((x & 0x0000ffff) << 16) | \
     ((x & 0xffff0000) >> 16))

#define STX_BE_PE_CONVERSION_INT16(x) \
    (((x & 0x00ff) << 8) | \
     ((x & 0xff00) >> 8))

#define STX_BE_PE_CONVERSION_INT32(x) \
    (((x & 0x000000ff) << 8) | \
     ((x & 0x0000ff00) >> 8) | \
     ((x & 0x00ff0000) << 8) | \
     ((x & 0xff000000) >> 8))

namespace stx {

template<class integer_type>
struct endianness_impl
{
    char char_[sizeof(integer_type)];
    integer_type integer_;
};

///////////////////////////////////////////////////////////////////////////////
//  LE <-> BE
///////////////////////////////////////////////////////////////////////////////

template<std::size_t size>
struct le_be_conversion_impl
{
    template<class T>
    static T convert(T x)
    {
        return x;
    }
};

template<>
struct le_be_conversion_impl<2>
{
    template<class T>
    static T convert(T x)
    {
        return STX_LE_BE_CONVERSION_INT16(x);
    }
};

template<>
struct le_be_conversion_impl<4>
{
    template<class T>
    static T convert(T x)
    {
        return STX_LE_BE_CONVERSION_INT32(x);
    }
};

#ifdef STX_HAS_INT64

template<>
struct le_be_conversion_impl<8>
{
    template<class T>
    static T convert(T x)
    {
        return STX_LE_BE_CONVERSION_INT64(x);
    }
};

#endif

template<class T>
inline T little_to_big_endian(T x)
{
    return le_be_conversion_impl<sizeof(T)>::convert(x);
}

template<class T>
inline T big_to_little_endian(T x)
{
    return le_be_conversion_impl<sizeof(T)>::convert(x);
}

///////////////////////////////////////////////////////////////////////////////
//  LE <-> PE
///////////////////////////////////////////////////////////////////////////////

template<std::size_t size>
struct le_pe_conversion_impl
{
    template<class T>
    static T convert(T x)
    {
        return x;
    }
};

template<>
struct le_pe_conversion_impl<2>
{
    template<class T>
    static T convert(T x)
    {
        return STX_LE_PE_CONVERSION_INT16(x);
    }
};

template<>
struct le_pe_conversion_impl<4>
{
    template<class T>
    static T convert(T x)
    {
        return STX_LE_PE_CONVERSION_INT32(x);
    }
};

template<class T>
inline T little_to_pdp_endian(T x)
{
    return le_pe_conversion_impl<sizeof(T)>::convert(x);
}

template<class T>
inline T pdp_to_little_endian(T x)
{
    return le_pe_conversion_impl<sizeof(T)>::convert(x);
}

///////////////////////////////////////////////////////////////////////////////
//  BE <-> PE
///////////////////////////////////////////////////////////////////////////////

template<std::size_t size>
struct be_pe_conversion_impl
{
    template<class T>
    static T convert(T x)
    {
        return x;
    }
};

template<>
struct be_pe_conversion_impl<2>
{
    template<class T>
    static T convert(T x)
    {
        return STX_BE_PE_CONVERSION_INT16(x);
    }
};

template<>
struct be_pe_conversion_impl<4>
{
    template<class T>
    static T convert(T x)
    {
        return STX_BE_PE_CONVERSION_INT32(x);
    }
};

template<class T>
inline T big_to_pdp_endian(T x)
{
    return be_pe_conversion_impl<sizeof(T)>::convert(x);
}

template<class T>
inline T pdp_to_big_endian(T x)
{
    return be_pe_conversion_impl<sizeof(T)>::convert(x);
}

///////////////////////////////////////////////////////////////////////////////
//  HOST <-> LE, BE, PE
///////////////////////////////////////////////////////////////////////////////

template<class T>
inline T host_to_little_endian(T src)
{
    T dst;
    switch (STX_HOST_BYTE_ORDER) {
    case STX_LITTLE_ENDIAN_BYTE_ORDER:
        dst = src;
        break;
    case STX_BIG_ENDIAN_BYTE_ORDER:
        dst = big_to_little_endian(src);
        break;
    case STX_PDP_ENDIAN_BYTE_ORDER:
        dst = pdp_to_little_endian(src);
        break;
    }
    return dst;
}

template<class T>
inline T host_to_big_endian(T src)
{
    T dst;
    switch (STX_HOST_BYTE_ORDER) {
    case STX_LITTLE_ENDIAN_BYTE_ORDER:
        dst = little_to_big_endian(src);
        break;
    case STX_BIG_ENDIAN_BYTE_ORDER:
        dst = src;
        break;
    case STX_PDP_ENDIAN_BYTE_ORDER:
        dst = pdp_to_big_endian(src);
        break;
    }
    return dst;
}

template<class T>
inline T host_to_pdp_endian(T src)
{
    T dst;
    switch (STX_HOST_BYTE_ORDER) {
    case STX_LITTLE_ENDIAN_BYTE_ORDER:
        dst = little_to_pdp_endian(src);
        break;
    case STX_BIG_ENDIAN_BYTE_ORDER:
        dst = big_to_pdp_endian(src);
        break;
    case STX_PDP_ENDIAN_BYTE_ORDER:
        dst = src;
        break;
    }
    return dst;
}

} // namespace stx

#endif // STX_CORE_ENDIANNESS_HPP
